123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- /**
- * 支持本地包直播链接
- * 传参 ?type=url¶ms=../json/live2cms.json
- live2cms.json
- 支持m3u类直播,支持线路归并。支持筛选切换显示模式
- [
- {
- "name": "GitHub",
- "url": "https://ghproxy.net/https://raw.githubusercontent.com/ssili126/tv/main/itvlist.txt"
- },
- {
- "name": "CNTV",
- "url": "./live_cntv.txt"
- }
- ]
- */
- /**
- * m3u直播格式转一般直播格式
- * @param m3u
- * @returns {string}
- */
- function convertM3uToNormal(m3u) {
- try {
- const lines = m3u.split('\n');
- let result = '';
- let TV = '';
- // let flag='#genre#';
- let flag = '#m3u#';
- let currentGroupTitle = '';
- lines.forEach((line) => {
- if (line.startsWith('#EXTINF:')) {
- line = line.replace(/'/g, '"');
- let groupTitle = '未知频道';
- let tvg_name = '';
- let tvg_logo = '';
- try {
- groupTitle = line.match(/group-title="(.*?)"/)[1].trim();
- } catch (e) {
- }
- try {
- tvg_name = line.match(/tvg-name="(.*?)"/)[1].trim();
- } catch (e) {
- }
- try {
- tvg_logo = line.match(/tvg-logo="(.*?)"/)[1].trim();
- } catch (e) {
- }
- TV = line.split(',').slice(-1)[0].trim();
- if (currentGroupTitle !== groupTitle) {
- currentGroupTitle = groupTitle;
- let ret_list = [currentGroupTitle, flag];
- // if(tvg_name){
- // ret_list.push(tvg_name);
- // }
- // if(tvg_logo){
- // ret_list.push(tvg_logo);
- // }
- result += `\n${ret_list.join(",")}\n`;
- }
- } else if (line.startsWith('http')) {
- const splitLine = line.split(',');
- result += `${TV}\,${splitLine[0]}\n`;
- }
- });
- // result = result.trim();
- result = mergeChannels(result);
- // log(result);
- return result
- } catch (e) {
- log(`m3u直播转普通直播发生错误:${e.message}`);
- return m3u
- }
- }
- /**
- * 线路归类/小棉袄算法
- * @param arr 数组
- * @param parse 解析式
- * @returns {[[*]]}
- */
- function splitArray(arr, parse) {
- parse = parse && typeof (parse) == 'function' ? parse : '';
- let result = [[arr[0]]];
- for (let i = 1; i < arr.length; i++) {
- let index = -1;
- for (let j = 0; j < result.length; j++) {
- if (parse && result[j].map(parse).includes(parse(arr[i]))) {
- index = j;
- } else if ((!parse) && result[j].includes(arr[i])) {
- index = j;
- }
- }
- if (index >= result.length - 1) {
- result.push([]);
- result[result.length - 1].push(arr[i]);
- } else {
- result[index + 1].push(arr[i]);
- }
- }
- return result;
- }
- /**
- * 搜索结果生成分组字典
- * @param arr
- * @param parse x=>x.split(',')[0]
- * @returns {{}}
- */
- function gen_group_dict(arr, parse) {
- let dict = {};
- arr.forEach((it) => {
- let k = it.split(',')[0];
- if (parse && typeof (parse) === 'function') {
- k = parse(k);
- }
- if (!dict[k]) {
- dict[k] = [it];
- } else {
- dict[k].push(it);
- }
- });
- return dict
- }
- /**
- * txt格式直播自动合并频道链接
- * @param text
- * @returns {string}
- */
- function mergeChannels(text) {
- const lines = text.split('\n');
- const channelMap = new Map();
- let currentChannel = ''; // 当前处理的频道
- lines.forEach(line => {
- // 使用正则表达式匹配频道行,假设频道行包含",#"即可识别为频道行
- if (/,#/.test(line)) {
- // 如果是频道名称,作为键值存储,初始化为空数组
- currentChannel = line;
- if (!channelMap.has(line)) {
- channelMap.set(line, []);
- }
- } else if (line) { // 忽略空行
- // 将当前行(链接)添加到当前频道数组中
- if (currentChannel) {
- channelMap.get(currentChannel).push(line);
- }
- }
- });
- // 构建结果字符串
- let result = '';
- channelMap.forEach((value, key) => {
- result += key + '\n' + value.join('\n') + '\n\n';
- });
- return result.trim(); // 移除尾部的多余换行符
- }
- globalThis.mergeChannels = mergeChannels;
- globalThis.convertM3uToNormal = convertM3uToNormal;
- globalThis.splitArray = splitArray;
- globalThis.gen_group_dict = gen_group_dict;
- globalThis.getRandomItem = function (items) {//从列表随机取出一个元素
- return items[Math.random() * items.length | 0];
- }
- globalThis.__ext = {data_dict: {}};
- var rule = {
- title: '直播转点播[合]',
- author: '道长',
- version: '20240628 beta7',
- update_info: `
- 20240628 beta6:
- 1.增加范冰冰v6源
- 2.修复带图标的m3u源识别
- 3.修复m3u8链接带参数转义问题
- 4.合并重复的频道名称下的链接
- 5.支持相对图片链接
- 20240627 beta1:
- 1.将原drpy项目的live2cms.js转换成hipy传参源。
- 【特别说明】支持m3u和txt的直播
- `,
- host: '',
- homeUrl: '',
- searchUrl: '#wd=**&pg=#TruePage##page=fypage',
- url: 'fyclass#pg=fypage&t=fyfilter',
- filter_url: '{{fl.show}}',
- headers: {'User-Agent': 'MOBILE_UA'},
- timeout: 5000, // class_name: '电影&电视剧&综艺&动漫',
- limit: 20,
- search_limit: 5, // 搜索限制取前5个,可以注释掉,就不限制搜索
- searchable: 1,//是否启用全局搜索,
- quickSearch: 0,//是否启用快速搜索,
- filterable: 1,//是否启用分类筛选,
- play_parse: true,
- // params: 'http://127.0.0.1:5707/files/json/live2cms.json',
- // 下面自定义一些源的配置
- // def_pic: 'https://avatars.githubusercontent.com/u/97389433?s=120&v=4', //默认列表图片
- def_pic: 'https://ghproxy.net/https://raw.githubusercontent.com/ls125781003/hikerjsonK/main/img/lives.jpg', //默认列表图片
- showMode: 'groups',// groups按组分类显示 all全部一条线路展示
- groupDict: {},// 搜索分组字典
- tips: '', //二级提示信息
- 预处理: $js.toString(() => {
- // 初始化保存的数据
- rule.showMode = getItem('showMode', 'groups');
- rule.groupDict = JSON.parse(getItem('groupDict', '{}'));
- rule.tips = `道长直播转点播js-当前版本${rule.version}`;
- if (typeof (batchFetch) === 'function') {
- // 支持批量请求直接放飞自我。搜索限制最大线程数量16
- rule.search_limit = 16;
- log('当前程序支持批量请求[batchFetch],搜索限制已设置为16');
- }
- let _url = rule.params;
- if (_url && typeof (_url) === 'string' && /^(http|file)/.test(_url)) {
- let html = request(_url);
- let json = JSON.parse(html);
- let _classes = [];
- rule.filter = {};
- rule.filter_def = {};
- json.forEach(it => {
- if (it.url && !/^(http|file)/.test(it.url)) {
- it.url = urljoin(_url, it.url);
- }
- if (it.img && !/^(http|file)/.test(it.img)) {
- it.img = urljoin(_url, it.img);
- }
- let _obj = {
- type_name: it.name,
- type_id: it.url,
- img: it.img,
- };
- _classes.push(_obj);
- let json1 = [{'n': '多线路分组', 'v': 'groups'}, {'n': '单线路', 'v': 'all'}];
- try {
- rule.filter[_obj.type_id] = [
- {'key': 'show', 'name': '播放展示', 'value': json1}
- ];
- if (json1.length > 0) {
- rule.filter_def[it.url] = {"show": json1[0].v};
- }
- } catch (e) {
- rule.filter[it.url] = json1
- }
- });
- __ext.data = json;
- rule.classes = _classes;
- }
- }),
- class_parse: $js.toString(() => {
- input = rule.classes;
- }),
- 推荐: $js.toString(() => {
- let update_info = [{
- vod_name: '更新日志',
- vod_id: 'update_info',
- vod_remarks: `版本:${rule.version}`,
- vod_pic: 'https://ghproxy.net/https://raw.githubusercontent.com/ls125781003/hikerjsonK/main/img/logo.png'
- }];
- VODS = [];
- if (rule.classes) {
- let randomClass = getRandomItem(rule.classes);
- let _get_url = randomClass.type_id;
- // let current_vod = rule.classes.find(item => item.type_id === _get_url);
- // let _pic = current_vod ? current_vod.img : '';
- let _pic = randomClass.img;
- let html;
- if (__ext.data_dict[_get_url]) {
- html = __ext.data_dict[_get_url];
- } else {
- html = request(_get_url);
- if (/#EXTM3U/.test(html)) {
- html = convertM3uToNormal(html);
- } else {
- html = mergeChannels(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- let arr = html.match(/.*?[,,]#[\s\S].*?#/g); // 可能存在中文逗号
- try {
- arr.forEach(it => {
- let vname = it.split(/[,,]/)[0];
- let vtab = it.match(/#(.*?)#/)[0];
- VODS.push({
- vod_name: vname,
- vod_id: _get_url + '$' + vname,
- vod_pic: _pic || rule.def_pic,
- vod_remarks: vtab,
- });
- });
- } catch (e) {
- log(`直播转点播获取首页推荐发送错误:${e.message}`);
- }
- }
- VODS = update_info.concat(VODS);
- }),
- 一级: $js.toString(() => {
- VODS = [];
- // 一级限制页数不允许翻页
- if (rule.classes && MY_PAGE <= 1) {
- if (MY_FL.show) {
- rule.showMode = MY_FL.show;
- setItem('showMode', rule.showMode);
- }
- let _get_url = input.split('#')[0];
- let current_vod = rule.classes.find(item => item.type_id === MY_CATE);
- let _pic = current_vod ? current_vod.img : '';
- let html;
- if (__ext.data_dict[_get_url]) {
- html = __ext.data_dict[_get_url];
- } else {
- html = request(_get_url);
- if (/#EXTM3U/.test(html)) {
- html = convertM3uToNormal(html);
- } else {
- html = mergeChannels(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- let arr = html.match(/.*?[,,]#[\s\S].*?#/g); // 可能存在中文逗号
- try {
- arr.forEach(it => {
- let vname = it.split(/[,,]/)[0];
- let vtab = it.match(/#(.*?)#/)[0];
- VODS.push({
- // vod_name:it.split(',')[0],
- vod_name: vname,
- vod_id: _get_url + '$' + vname,
- vod_pic: _pic || rule.def_pic,
- vod_remarks: vtab,
- });
- });
- } catch (e) {
- log(`直播转点播获取一级分类页发生错误:${e.message}`);
- }
- }
- }),
- 二级: $js.toString(() => {
- VOD = {};
- if (orId === 'update_info') {
- VOD = {
- vod_content: rule.update_info.trim(),
- vod_name: '更新日志',
- type_name: '更新日志',
- vod_pic: 'https://resource-cdn.tuxiaobei.com/video/FtWhs2mewX_7nEuE51_k6zvg6awl.png',
- vod_remarks: `版本:${rule.version}`,
- vod_play_from: '道长在线',
- // vod_play_url: '嗅探播放$https://resource-cdn.tuxiaobei.com/video/10/8f/108fc9d1ac3f69d29a738cdc097c9018.mp4',
- vod_play_url: '随机小视频$http://api.yujn.cn/api/zzxjj.php',
- };
- } else {
- if (rule.classes) {
- let _get_url = orId.split('$')[0];
- let _tab = orId.split('$')[1];
- if (orId.includes('#search#')) {
- let vod_name = _tab.replace('#search#', '');
- let vod_play_from = '来自搜索';
- vod_play_from += `:${_get_url}`;
- let vod_play_url = rule.groupDict[_get_url].map(x => x.replace(',', '$')).join('#');
- log(orId);
- VOD = {
- vod_name: '搜索:' + vod_name,
- type_name: "直播列表",
- vod_pic: rule.def_pic,
- // vod_content: orId,
- vod_content: orId.replace(getHome(orId), 'http://***'),
- vod_play_from: vod_play_from,
- vod_play_url: vod_play_url,
- vod_director: rule.tips,
- vod_remarks: rule.tips,
- }
- } else {
- let current_vod = rule.classes.find(item => item.type_id === _get_url);
- let _pic = current_vod ? current_vod.img : '';
- let html;
- if (__ext.data_dict[_get_url]) {
- html = __ext.data_dict[_get_url];
- } else {
- html = request(_get_url);
- if (/#EXTM3U/.test(html)) {
- html = convertM3uToNormal(html);
- } else {
- html = mergeChannels(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- let a = new RegExp(`.*?${_tab.replace('(','\\(').replace(')','\\)')}[,,]#[\\s\\S].*?#`);
- let b = html.match(a)[0];
- let c = html.split(b)[1];
- if (c.match(/.*?[,,]#[\s\S].*?#/)) {
- let d = c.match(/.*?[,,]#[\s\S].*?#/)[0];
- c = c.split(d)[0];
- }
- let arr = c.trim().split('\n');
- let _list = [];
- arr.forEach((it) => {
- if (it.trim()) {
- let t = it.trim().split(',')[0];
- let u = it.trim().split(',')[1];
- _list.push(t + '$' + u);
- }
- });
- let vod_name = __ext.data.find(x => x.url === _get_url).name;
- let vod_play_url;
- let vod_play_from;
- if (rule.showMode === 'groups') {
- let groups = splitArray(_list, x => x.split('$')[0]);
- let tabs = [];
- for (let i = 0; i < groups.length; i++) {
- if (i === 0) {
- tabs.push(vod_name + '@1');
- } else {
- tabs.push(`@${i + 1}`);
- }
- }
- vod_play_url = groups.map(it => it.join('#')).join('$$$');
- vod_play_from = tabs.join('$$$');
- } else {
- vod_play_url = _list.join('#');
- vod_play_from = vod_name;
- }
- log(orId);
- VOD = {
- vod_id: orId,
- vod_name: vod_name + '|' + _tab,
- type_name: "直播列表",
- vod_pic: _pic || rule.def_pic,
- // vod_content: orId,
- vod_content: orId.replace(getHome(orId), 'http://***'),
- vod_play_from: vod_play_from,
- vod_play_url: vod_play_url,
- vod_director: rule.tips,
- vod_remarks: rule.tips,
- };
- }
- }
- }
- }),
- 搜索: $js.toString(() => {
- VODS = [];
- if (rule.classes && MY_PAGE <= 1) {
- let _get_url = __ext.data[0].url;
- let current_vod = rule.classes.find(item => item.type_id === _get_url);
- let _pic = current_vod ? current_vod.img : '';
- let html;
- if (__ext.data_dict[_get_url]) {
- html = __ext.data_dict[_get_url];
- } else {
- html = request(_get_url);
- if (/#EXTM3U/.test(html)) {
- html = convertM3uToNormal(html);
- } else {
- html = mergeChannels(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- let str = '';
- Object.keys(__ext.data_dict).forEach(() => {
- str += __ext.data_dict[_get_url];
- });
- let links = str.split('\n').filter(it => it.trim() && it.includes(',') && it.split(',')[1].trim().startsWith('http'));
- links = links.map(it => it.trim());
- let plays = Array.from(new Set(links));
- log('搜索关键词:' + KEY);
- log('过滤前:' + plays.length);
- // plays = plays.filter(it => it.includes(KEY));
- plays = plays.filter(it => new RegExp(KEY, 'i').test(it));
- log('过滤后:' + plays.length);
- log(plays);
- let new_group = gen_group_dict(plays);
- rule.groupDict = Object.assign(rule.groupDict, new_group);
- // 搜索分组结果存至本地方便二级调用
- setItem('groupDict', JSON.stringify(rule.groupDict));
- // 返回的还是搜索的new_group
- Object.keys(new_group).forEach((it) => {
- VODS.push({
- 'vod_name': it,
- 'vod_id': it + '$' + KEY + '#search#',
- 'vod_pic': _pic || rule.def_pic,
- });
- });
- }
- }),
- lazy: $js.toString(() => {
- if (/\.(m3u8|mp4)/.test(input)) {
- if (input.includes('?') && typeof (playObj) == 'object' && playObj.url) {
- input = playObj.url;
- }
- input = {parse: 0, url: input}
- } else if (/yangshipin|1905\.com/.test(input)) {
- input = {parse: 1, jx: 0, url: input, js: '', header: {'User-Agent': PC_UA}, parse_extra: '&is_pc=1'};
- } else {
- input
- }
- }),
- }
|